//
// Animation player for Code Red v2 worm model
//
// Michael Liljenstam
// ISTS
//
// $Id: WormAnimPlayer.java,v 1.2 2002/02/01 18:40:55 mili Exp mili $
//


import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
import javax.swing.event.*;

import java.text.*;
import java.awt.font.*;

import SSF.Net.*;
import SSF.Util.Streams.*;
import SSF.OS.NetFlow.*;
import SSF.OS.NetFlow.Filter.*;
import com.renesys.raceway.DML.*;

import com.renesys.raceway.views.*;

import SSF.Util.Plot.*;

/** This Stream.Player implementation provides default 
 *  implementations for the describe() method (to 
 *  render records as strings) and the getControls() method.
 *  It will not be part of Raceway in the long run; instead, 
 *  it can be transitioned into SSF.Util.Plot. 
 * 
 */
public class WormAnimPlayer implements Stream.Player {

    private static final boolean globalPlotOn = true;
    private static final boolean routingTrafficPlotOn = true;

    //    private static final boolean routeUpdateAnimOn = true;
    private static final boolean routeUpdateAnimOn = false;

    // attrs

    TimeSeriesComponent gsp = null; // Global State Plot
    TimeSeriesComponent rtp = null; // Routing Traffic Plot

    double _lastTime = 0; // workaround for plot reset

    private static final byte ROUTER_STRESS_STATE_REC = 0;
    private static final byte ROUTING_STATE_REC = 1;
    private static final byte GLOBAL_STATE_REC = 2;
    private static final byte GLOBAL_ROUTING_TRAFFIC_REC = 3;

    private static final byte NORMAL  = 0;
    private static final byte CRASHED = 1;

    private static final byte ACTIVE = 2;
    private static final byte REBOOT = 3;



    //----------------------------------------------------------------------

    public WormAnimPlayer() {
	fm.setMinimumFractionDigits(3);
	fm.setMaximumFractionDigits(3);

	if (globalPlotOn) {
	  // create plot window

	    //	    int winHeight = 680; // pixels
	    int winHeight = 250; // pixels
	    try {
	      gsp = new TimeSeriesComponent(1, "Global State Monitor", 
						   winHeight);
	    } catch (Exception e) {
	      System.err.println("ERROR: Couldn't create time-series plot window. Exiting.");
	      System.exit(1);
	    }

	    // hardcoded for now...

	    gsp.setTitle("Worm infection");
	    gsp.setBinSize(500.0F); //- optional
	    //	    gsp.setBinSize(1.0F); //- optional, default
	    //	    gsp.setTimeWindowSize(386); // moving window, time units

	    //	    gsp.getPlot(0).setDatasetName("h1-1");
	    gsp.getPlot(0).setFieldSelectorName("i(t)");
	    /*
	      gsp.getPlot(1).setDatasetName("r1-0");
	      gsp.getPlot(1).setFieldSelectorName("pkt");
	      gsp.getPlot(2).setDatasetName("r1-0");
	      gsp.getPlot(2).setFieldSelectorName("qlen");
	      gsp.getPlot(3).setDatasetName("r1000-0");
	      gsp.getPlot(3).setFieldSelectorName("pkt");
	    */
	} // if

	if (routingTrafficPlotOn) {
	  // create plot window
	    int winHeight = 250; // pixels
	    try {
	      rtp = new TimeSeriesComponent(1, "Routing Traffic Monitor", 
					    winHeight);
	    } catch (Exception e) {
	      System.err.println("ERROR: Couldn't create time-series plot window. Exiting.");
	      System.exit(1);
	    }

	    // hardcoded for now...

	    rtp.setTitle("Observed Routing Traffic at one Core AS");
	    //	    rtp.setBinSize(1.0F); //- optional, default
	    rtp.setTimeWindowSize(386); // moving window, time units

	    rtp.getPlot(0).setDatasetName("#updates");
	    //	    rtp.getPlot(0).setFieldSelectorName("#updates");
	} // if

    }

    //----------------------------------------------------------------------

    public static long MIN_DURATION = 10; // ms
    private void pulse(Raceway.VisibleHost h, double x) {
	long ms0 = System.currentTimeMillis();
	int lx = (int)(Math.log(x)/Math.log(10));
	int rx = (lx>0?25*lx:0); 
	if (rx>255) rx=255;
	Color c = new Color(rx,rx/2,rx/2);
	while (System.currentTimeMillis()-ms0 < MIN_DURATION) {
	    h.setFillColor(c);
	    h.scaleSize(4.);
	    h.refresh();
	    h.scaleSize(0.25);
	    h.refresh();
	}
    }
    public void play(Stream.Record r) {
	String rty = 
	    stream.getRecordTypeString(r.type_code);
	String nhi = 
	    stream.getRecordSourceString(r.source_code);
	if ("SSF.Model.WormRecord".equals(rty)) {

	    // animation
	    Raceway.VisibleHost h = stream.raceway().getVisibleHost(nhi);
            if (rty.equals("SSF.Model.WormRecord")) {
		double time  = r.timestamp;
		int offset = r.offset;
		byte type = r.bytes[offset+0];
		byte state = r.bytes[offset+1];

		switch (type) {
		case ROUTER_STRESS_STATE_REC:
		  //		  System.err.println("STRESS");
		  switch (state) {
		  case CRASHED:
		    color(h,(byte) CRASHED, 0);
		    break;
		  case NORMAL:
		    color(h,(byte) REBOOT, 0);
		    break;
		  }
		  break;
		case ROUTING_STATE_REC:
		  if (routeUpdateAnimOn) {
		    int activity = BytesUtil.bytesToInt(r.bytes, offset+2);
		    if (activity > 0) {
			color(h,(byte) ACTIVE, activity);
			//		    System.err.println("ACTIVE");
		    } else {
			color(h,(byte) NORMAL, 0);
			//		    System.err.println("NORMAL");
		    }
		  }
		  break;
		case GLOBAL_STATE_REC:
		  //		  System.err.println("GLOBAL");
		  // time series plot
		  float i = BytesUtil.bytesToFloat(r.bytes, offset+1);
		  plotBarGlobal(time, i);

		  break;
		case GLOBAL_ROUTING_TRAFFIC_REC:
		  //		  System.err.println("GLOBAL_ROUTING");
		  // time series plot
		  int rt = BytesUtil.bytesToInt(r.bytes, offset+1);
		  plotBarRoutingTraffic(time, rt);

		  break;
		}

	    }

	}
    }


     private void color(Raceway.VisibleHost h, byte a, int activity) {
	 Color c = null;
	 /*
	 if (a > 0) {
	     c = new Color(0,0,220);
	 } else {
	     c = new Color(0,191,220);
	 }
	 */
	 switch (a) {
	 case NORMAL:
	     //	   c = new Color(127,255,127);
	   c = new Color(0,255,0);
	   break;
	 case CRASHED:
	   c = new Color(0,0,0);
	   break;
	 case ACTIVE:
	   c = new Color(Math.min(255, (activity-1)*20),0,220);
	   //	   c = new Color(Math.min(255, activity-1),0,220);
	   break;
	 case REBOOT:
	     //	   c = new Color(0,230,230);
	   c = new Color(0,255,255);
	   break;
	 }
	 h.setFillColor(c);
	 h.refresh();
    }


    public void plotBarGlobal(double time, float i) {

	// workaround for plot reset
	if (time < _lastTime) {
	    gsp.resetAll();
	}
	_lastTime = time;
		
	// time series
	gsp.addPoint(0, time, i);
    }

    public void plotBarRoutingTraffic(double time, float i) {

	// workaround for plot reset
	if (time < _lastTime) {
	    rtp.resetAll();
	}
	_lastTime = time;
		
	// time series
	rtp.addPoint(0, time, i);
    }


    public String toString() {
	return "Worm Model Event Monitor";
    }

    protected Stream stream;

    public void setStream(Stream stream) {
        this.stream = stream;
    }

    public void start(Stream stream) {
	this.stream = stream;
    }
    public void finish(Stream stream) {
	//this.stream = null; // too harsh
    }

    //----------------------------------------------------------------------
    // OPTIONS 

    boolean[] options;
    String nhiFilter;

    private JComponent controls;

    public synchronized Component getControls() {
	if (null==controls) {
	    JLabel sorry = new JLabel("No configurable content");
	    sorry.setForeground(Color.blue);
	    controls = new JPanel();
	    controls.add(sorry);
	}
	return controls;
    }

    //----------------------------------------------------------------------

  java.text.DecimalFormat fm = new DecimalFormat();


    public static final boolean IGNORE_ALL = false; // true;
    public AttributedString describe(Stream.Record record) {
	if (IGNORE_ALL)
	    return null;
	
	int tid   = record.type_code;
	int sid    = record.source_code;
	double time  = record.timestamp;
	byte[] buf   = record.bytes;
	int offset   = record.offset;
	int length   = record.length;
	
	String ret = null;
	Color newColor = Color.black;
	String src;
	String typ;

	if (stream.getRecordTypeString(tid).equals("SSF.Model.WormRecord")) {

	    String src_nhi = stream.getRecordSourceString(sid);

	    if (stream.getRecordTypeString(tid).equals("SSF.Model.WormRecord")) {
		byte type = buf[offset+0];
		byte state = buf[offset+1];
		String stateStr = null;

		switch (type) {
		case ROUTER_STRESS_STATE_REC:
		  switch (state) {
		  case NORMAL:
		    stateStr = " reboot";
		    break;
		  case CRASHED:
		    stateStr = " crash";
		    break;
		  }
		  ret = (fm.format(time) + " " + src_nhi
			 + " router "  + stateStr);

		  // animation
		  newColor = new Color(255,127,127);

		  break;
		case ROUTING_STATE_REC:
		  int activity = BytesUtil.bytesToInt(buf, offset+2);

		  if (activity > 0) {
		      ret = (fm.format(time) + " " + src_nhi
			     + " router processing " + activity + 
			     " routing update(s)");
		  } else {
		      ret = (fm.format(time) + " " + src_nhi
			     + " router idle ");
		  }

		  // animation
		  newColor = new Color(127,255,127);

		  break;
		case GLOBAL_STATE_REC:
		  float i = BytesUtil.bytesToFloat(buf, offset+1);

		  ret = (fm.format(time) + " Global state: i=" + i);

		  // animation
		  newColor = new Color(0,200,200);

		  break;
		case GLOBAL_ROUTING_TRAFFIC_REC:
		  float rt = BytesUtil.bytesToInt(buf, offset+1);

		  ret = (fm.format(time) + " " + src_nhi
			 + " Obs. routing traffic: " + rt);

		  // animation
		  newColor = new Color(0,200,200);

		  break;
		}
	    }

	    AttributedString aret = new AttributedString(ret.trim());
	    aret.addAttribute(TextAttribute.FOREGROUND, newColor);
	    return aret;

	} else {
	    //	    if (!stream.getRecordTypeString(tid).equals("SSF.OS.NetFlow")) {
	    //		System.err.println("WARNING: Not a recognized record!");
	    //	    }
	    return null;
	}
    }
}
